package org.javahispano.javacup.tacticas_aceptadas.rolandun.romedalus.tactica;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.javahispano.javacup.modelo.Comando;
import org.javahispano.javacup.modelo.ComandoGolpearBalon;
import org.javahispano.javacup.modelo.ComandoIrA;
import org.javahispano.javacup.modelo.Constantes;
import org.javahispano.javacup.modelo.JugadorDetalle;
import org.javahispano.javacup.modelo.Posicion;
import org.javahispano.javacup.modelo.SituacionPartido;
import org.javahispano.javacup.modelo.Tactica;
import org.javahispano.javacup.modelo.TacticaDetalle;

import org.javahispano.javacup.tacticas_aceptadas.rolandun.romedalus.util.Balon;
import org.javahispano.javacup.tacticas_aceptadas.rolandun.romedalus.util.Estadistica;
import org.javahispano.javacup.tacticas_aceptadas.rolandun.romedalus.util.IConstantesRomedalusTeam;

/**
 * @author Roland Cruz
 */
/**
 * @author Roland
 */
public class TacticaRomedalusTeam implements Tactica, Alineaciones, IConstantesRomedalusTeam {

    private ArrayList<Comando> comandos = new ArrayList<Comando>();
    private TacticaDetalle detalle = new TacticaRomedalusTeamDetalleImpl();
    private boolean paseAtras = false;
    private int idxPortRival = -1;																	// indica el indice del portero rival
    private int idxMiPortero = 0;																	// indica el indice de mi portero
    private int iterSaco = 0;																	// iteraciones para sacar
    private int goles = 0;																	// goles mios
    private Balon balonAnt = null;																// Informacion del balon en la iteracion anterior
    private Balon balonSig = null;																// Informacion del balon en la iteracion anterior
    private SituacionPartido sp;																					// situacion del partido actual
    boolean laTengo = false;
    LinkedHashMap<Integer, LinkedHashMap<tipoOpcion, Estadistica>> estadisticas = new LinkedHashMap<Integer, LinkedHashMap<tipoOpcion, Estadistica>>();

    /** Variables usadas para estadisticas de tiros al arco */
    private enum tipoOpcion {

        tLargo, tMedio, tCerca, globo, pase, paseAngulo, avanzar, paseGlobo, tCalculado, tNoCalculado
    };
    private Map<tipoOpcion, Integer> opcionesGol = new HashMap<tipoOpcion, Integer>();
    private tipoOpcion ultimoTiro = null;
    private int ultimoJugador = -1;

    private void addOpcion(tipoOpcion to) {
        if (opcionesGol.get(to) == null || opcionesGol.get(to) < 0) {
            opcionesGol.put(to, 0);
        }
        opcionesGol.put(to, opcionesGol.get(to) + 1);
    }

    @Override
    public TacticaDetalle getDetalle() {
        return detalle;
    }

    @Override
    public List<Comando> ejecutar(SituacionPartido sp) {
        inicializar(sp);
        // si tengo el balon busco pasarlo, controlarlo o rematar al arco
        if (tengoElBalon()) {
            ultimoTiro = null;
            ultimoJugador = -1;
            laTengo = true;
            pasarBalon();
        } // si el rival tiene el balon busco recuperarlo, indicando que no es un balon dividido
        else if (tieneElBalon()) {
            paseAtras = false;
            laTengo = false;
            ultimoTiro = null;
            ultimoJugador = -1;
            recuperarBalon();
        } // cuando el balon está dividido, lo recupero si no soy el mas cercano a el
        else if (balonDividido()) {
            buscarBalon();
        }

        // Método encargado de definir la funcionalidad de mi portero
        ubicarPortero();

        // se ubica la alineacion adecuada
        setAlineacion();
        balonAnt = new Balon(sp.balon(), sp.alturaBalon());

        if (sp.iteracion() == Constantes.ITERACIONES - 1) {
            if (false) {// poner en true para mostrar estadisticas
                if (opcionesGol.size() > 0) {
                    //System.out.println();
                    for (tipoOpcion to : opcionesGol.keySet()) {
                        System.out.print(to + " " + opcionesGol.get(to) + "  ");
                    }
                }
                opcionesGol = new HashMap<tipoOpcion, Integer>();
                ultimoTiro = null;
                goles = 0;
            }
        }
        return comandos;
    }

    /** Logica inicial de cada iteracion */
    private void inicializar(SituacionPartido sp) {
        comandos = new ArrayList<Comando>();
        // variable global para facilitar los demas metodos
        this.sp = sp;
        if (balonAnt == null) {
            balonAnt = new Balon(sp.balon(), sp.alturaBalon());
        }
        // se obtiene la siguiente posicion del balon
        balonSig = obtenerPosBalon();
        // se obtiene el id del portero rival
        setPorteroRival();

        if (ultimoJugador >= 0) {
            LinkedHashMap<tipoOpcion, Estadistica> opciones = estadisticas.get(ultimoJugador);
            // logica para manejar las opciones de gol
            if (sp.golesPropios() > 0 && sp.golesPropios() != goles && ultimoTiro != null) {
                goles = sp.golesPropios();
                addOpcion(ultimoTiro);
                opciones.get(ultimoTiro).addGol();
                opciones.get(ultimoTiro).setOpciones(1);
                opciones.get(ultimoTiro).setDescartes(0);
            }
        }
    }

    /** Metodo encargado de ubicar al portero segun cada iteracion */
    private void ubicarPortero() {
        for (int i = 0; i < comandos.size(); i++) {
            Comando com = comandos.get(i);
            if (com.getIndJugador() == idxMiPortero) {
                comandos.remove(i);
                i = -1;
            }
        }
        // El comportamiento es diferente cuando saco
        if (!sp.saco()) {
            Posicion pDespeje = getDireccionAvance(idxMiPortero);
            if (pDespeje.getY() < Constantes.centroArcoInf.getY()) {
                pDespeje = new Posicion(-35, 0);
                if (sp.balon().getX() > 0) {
                    pDespeje = new Posicion(35, 0);
                }
            }
            if (sp.balon().distancia(Constantes.centroArcoInf) < 10 && sp.alturaBalon() <= Constantes.ALTURA_CONTROL_BALON && sp.balon().indiceMasCercano(sp.misJugadores()) == idxMiPortero) {
                comandos.add(new ComandoIrA(idxMiPortero, balonSig.getPosicion()));
                comandos.add(new ComandoGolpearBalon(idxMiPortero, pDespeje, 1, ANG_DESPEJE));
            } else {
                boolean movimiento = false;
                // si el balon está cerca al arco y viene hacia mi por arriba
                if (sp.balon().getY() < -30 && aMiArco() && sp.alturaBalon() > 0 && sp.puedenRematarRival().length == 0) {
                    // se busca la intercepcion del disparo al arco
                    Posicion p = Posicion.Interseccion(sp.balon(), balonSig.getPosicion(), Constantes.posteDerArcoInf, Constantes.posteIzqArcoInf);
                    // si el balon tiene direccion al arco
                    if (p != null && Math.abs(p.getX()) <= LARGO_MEDIO_ARCO + 0.1d) {
                        if (balonSig.getAltura() <= 3 && sp.misJugadores()[idxMiPortero].distancia(balonSig.getPosicion()) <= sp.misJugadores()[idxMiPortero].distancia(p)) {
                            comandos.add(new ComandoIrA(idxMiPortero, balonSig.getPosicion().setDentroCampoJuego()));
                        } else {
                            comandos.add(new ComandoIrA(idxMiPortero, p.moverPosicion(0, 0.75d).setDentroCampoJuego()));
                        }
                        if (sp.balon().distancia(sp.misJugadores()[idxMiPortero]) <= Constantes.DISTANCIA_CONTROL_BALON_PORTERO) {
                            comandos.add(new ComandoGolpearBalon(idxMiPortero, pDespeje, 1, ANG_DESPEJE));
                        }
                        movimiento = true;
                    }
                }
                if (!movimiento) {
                    if (!aMiArco() && sp.alturaBalon() <= Constantes.ALTURA_CONTROL_BALON && sp.balon().getY() + 5.5 < Constantes.penalInf.getY() && sp.balon().getX() > -8 && sp.balon().getX() < 8) {
                        if ((sp.puedenRematar().length > 0 && sp.puedenRematar()[0] == idxMiPortero) || sp.balon().indiceMasCercano(sp.misJugadores()) == idxMiPortero) {
                            // ubico al portero interceptando la siguiente posicion del balon
                            comandos.add(new ComandoIrA(idxMiPortero, balonSig.getPosicion()));
                            if (sp.balon().distancia(sp.misJugadores()[idxMiPortero]) <= Constantes.DISTANCIA_CONTROL_BALON_PORTERO) {
                                // se busca e jugador mas cercano desmarcado
                                int idxPase = getDesmarcado(idxMiPortero);
                                if (idxPase >= 0) {// pase por arriba
                                    comandos.add(new ComandoGolpearBalon(idxMiPortero, sp.misJugadores()[idxPase], 1, ANG_DESPEJE));
                                } else {// despeje al lugar mas desocupado de la cancha
                                    comandos.add(new ComandoGolpearBalon(idxMiPortero, pDespeje, 1, ANG_DESPEJE));
                                }
                            }
                            movimiento = true;
                        }
                    }
                    if (!movimiento) {
                        double angulo = Constantes.centroArcoInf.angulo(sp.balon());
                        comandos.add(new ComandoIrA(idxMiPortero, Constantes.centroArcoInf.moverAngulo(angulo, 5.)));
                        movimiento = true;
                    }
                }
            }

        } else {
            double angulo = Constantes.centroArcoInf.angulo(sp.balon());
            comandos.add(new ComandoIrA(idxMiPortero, Constantes.centroArcoInf.moverAngulo(angulo, 5.)));
        }
    }

    /** se determina si el balon va rumbo a mi arco */
    private boolean aMiArco() {
        if (balonAnt != null) {
            return balonAnt.getPosicion().getY() > sp.balon().getY();
        }
        return false;
    }

    private boolean tengoElBalon() {
        return sp.saco() || sp.puedenRematar().length > 0;
    }

    private boolean tieneElBalon() {
        return sp.sacaRival() || sp.puedenRematarRival().length > 0;
    }

    private boolean balonDividido() {
        return !sp.saco() && !sp.sacaRival() && sp.puedenRematar().length == 0 && sp.puedenRematarRival().length == 0;
    }

    /** Se obtiene el indicador del arquero (no necesariamente siempre es el 1) */
    private void setPorteroRival() {
        if (idxPortRival < 0) {
            JugadorDetalle[] rivales = sp.detalleJugadoresRivales();
            for (int i = 0; i < rivales.length; i++) {
                if (rivales[i].esPortero()) {
                    idxPortRival = i;
                    break;
                }
            }
        }
    }

    /** Se intenta obtener el balon antes que el rival */
    private boolean buscarBalon() {
        recuperarBalon();
        return true;
    }

    /**
     * Se intenta quitarle el balon al rival
     *
     * @param balonDividido
     */
    private boolean recuperarBalon() {
        Posicion pBalon = sp.balon();
        // se obtiene el rival mas cercano al balon
        int cercanoEl = pBalon.indiceMasCercano(sp.rivales());
        Posicion cercanoBalonEl = sp.rivales()[cercanoEl];
        // el jugador mas cercano va hacia el balon
        // int idxCorta = -1;
        int idxCorta = getPosFinalBalon();
        int cerca = balonSig.getPosicion().indiceMasCercano(sp.misJugadores(), idxMiPortero);
        if (cerca >= 0) {
            comandos.add(new ComandoIrA(cerca, obtenerPosDestino(cerca)));
        }

        // jugadores mas cercanos al balon sin tener en cuenta mi arquero
        int[] pCercanos = pBalon.indicesMasCercanos(sp.misJugadores(), idxMiPortero, cerca);
        List<Integer> marcando = new ArrayList<Integer>();
        marcando.add(cerca);
        marcando.add(idxCorta);

        // se intenta impedir la movilidad del rival
        for (int i = 0; i < 11; i++) {
            int idx = pCercanos[i];
            if (!marcando.contains(idx) && cercanoBalonEl.getY() >= sp.misJugadores()[idx].getY()) {
                comandos.add(new ComandoIrA(idx, balonSig.getPosicion()));
                marcando.add(idx);
                break;
            }
        }
        // se marcan los rivales cercanos a mi arco
        int[] cercanos = Constantes.centroArcoInf.indicesMasCercanos(sp.rivales());
        if (aMiArco() || sp.balon().getY() <= INICIO_DEFENSA) {
            for (int i : cercanos) {
                Posicion rival = sp.rivales()[i];
                if (rival.getY() < INICIO_DEFENSA) {
                    int[] cercanosAtacante = rival.indicesMasCercanos(sp.misJugadores(), idxMiPortero);
                    for (int c : cercanosAtacante) {
                        if (!marcando.contains(c)) {
                            double a = rival.angulo(sp.balon());
                            Posicion p = rival.moverAngulo(a, DISTANCIA_MARCA_RIVAL);
                            comandos.add(new ComandoIrA(c, p));
                            marcando.add(c);
                            break;
                        }
                    }
                } else {
                    break;
                }
            }
        }
        return true;
    }

    /** Se intenta pasar el balon o darle al arco rival */
    private void pasarBalon() {
        int[] cercanos = sp.balon().indicesMasCercanos(sp.misJugadores());
        if (sp.saco() || sp.balon().distancia(Constantes.centroCampoJuego) == 0) {
            if (sp.balon().distancia(Constantes.centroCampoJuego) == 0) {
                iterSaco = ITERACIONES_SAQUE;
            }
            sacar(cercanos);
        } else {
            boolean pase = false;
            for (int i : sp.puedenRematar()) {
                if (i != idxMiPortero) {
                    // si el jugador que remata esta a menos de 24 metros del arco rival -> remata al arco
                    double distJugadorArco = sp.misJugadores()[i].distancia(Constantes.centroArcoSup);
                    if (distJugadorArco < MAX_DISTANACIA_TIRO_AL_ARCO || sp.balon().getY() > 45) {
                        pase = lanzarAlArco(i);
                    }
                    if (!pase) {
                        // mandar el balon lo mas lejos posible cuando está en mi area
                        if (sp.balon().getY() < Constantes.penalInf.getY()) {
                            comandos.add(new ComandoGolpearBalon(i, getDireccionAvance(i), 1, ANG_DESPEJE));
                        } else {

                            // se intenta realizar un pase seguro rastrero
                            if (!intentarPaseRastrero(i, cercanos, true)) {
                                // se intenta realizar un pase seguro elevado
                                if (!intentarPaseElevado(i, cercanos, true)) {
                                    // se busca el angulo de avance mas despoblado
                                    double ang = getMejorOpcionAvance(i);
                                    // pase al jugador mas desmarcado
                                    if (ang != -1 && ang < 90 && ang > -90) {
                                        Posicion p = getDireccionAvance(sp.misJugadores()[i], ang);
                                        comandos.add(new ComandoGolpearBalon(i, p, getVelocidadAvance(i), 0));
                                        pase = true;
                                    }
                                    if (!pase) {
                                        // se intenta realizar un pase seguro por tierra hacia atras
                                        if (!intentarPaseRastrero(i, cercanos, false)) {
                                            if (ang != -1) {
                                                Posicion p = getDireccionAvance(sp.misJugadores()[i], ang);
                                                if (sp.balon().distancia(p) <= DISTANCIA_MAXIMA_PASE) {
                                                    comandos.add(new ComandoGolpearBalon(i, p, getVelocidadAvance(i), 0));
                                                    pase = true;
                                                }
                                            }
                                            if (!pase) {
                                                if (!intentarPaseElevado(i, cercanos, false)) {
                                                    // se intenta realizar un pase seguro elevado hacia atras
                                                    if (sp.balon().getY() > 0) {
                                                        comandos.add(new ComandoGolpearBalon(i, Constantes.centroArcoSup, 1, ANG_DESPEJE));
                                                    } else {
                                                        comandos.add(new ComandoGolpearBalon(i, getDireccionAvance(i), 1, ANG_DESPEJE));
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private boolean intentarPaseRastrero(int i, int[] mios, boolean adelante) {
        int idxPase = -1;
        int cont = 0;
        for (int j = 1; j < 11; j++) {
            if (cont < 5) {
                int pase = mios[j];
                if (pase != i && pase != idxMiPortero) {
                    Posicion pPase = sp.misJugadores()[pase];
                    // si no hay obstaculo por tierra hacia el posible pase
                    if (((pPase.getY() > sp.balon().getY() && adelante) || !adelante)) {
                        cont++;
                        if (!obstaculoBajo(pPase, false)) {
                            idxPase = pase;
                            break;
                        }
                    }
                }
            }
        }
        // si hay pase posible hacia adelante
        if (idxPase >= 0) {
            comandos.add(new ComandoGolpearBalon(i, sp.misJugadores()[idxPase], getVelocidadPase(i, idxPase), 0));
            comandos.add(new ComandoIrA(idxPase, sp.balon()));
            return true;
        }
        return false;
    }

    private boolean intentarPaseElevado(int i, int[] mios, boolean adelante) {
        int idxPase = -1;
        int cont = 0;
        for (int j = 1; j < 11; j++) {
            if (cont < 5) {
                int pase = mios[j];
                if (pase != i && pase != idxMiPortero) {
                    Posicion pPase = sp.misJugadores()[pase];
                    // si no hay obstaculo por aire hacia el posible pase
                    if (((pPase.getY() > sp.balon().getY() && adelante) || !adelante)) {
                        cont++;
                        if (!obstaculoElevado(pPase, false)) {
                            idxPase = pase;
                            break;
                        }
                    }
                }
            }
        }
        // si hay pase posible hacia adelante
        if (idxPase >= 0) {
            golperBalonAlto(i, sp.misJugadores()[idxPase]);
            return true;
        }
        return false;
    }

    private double getVelocidadAvance(int i) {
        return 0.98d / Constantes.getVelocidadRemate(sp.detalleMisJugadores()[i].getRemate());
    }

    private boolean obstaculoBajo(Posicion p, boolean alArco) {
        int[] rivales = sp.balon().indicesMasCercanos(sp.rivales());
        double distPuntoBalon = sp.balon().distancia(p);
        for (int r = 0; r < rivales.length; r++) {
            int i = rivales[r];
            Posicion rival = sp.rivales()[i];
            double distRivalBalon = rival.distancia(sp.balon());
            double distRivalPunto = rival.distancia(p);
            if (i != idxPortRival && rival.getY() < 47) {
                double angBalonRival = sp.balon().angulo(rival);
                double angDisparo = sp.balon().angulo(p);
                double difAng = difAngulosRadianes(angBalonRival, angDisparo);
                if (alArco && difAng <= 12 && distRivalBalon <= distPuntoBalon && distRivalPunto <= distPuntoBalon) {
                    return true;
                } else if (!alArco && ((difAng <= 20 && distRivalBalon <= distPuntoBalon && distRivalPunto <= distPuntoBalon) || distRivalPunto < DIST_MARCA || (distPuntoBalon < 4 && sp.balon().getY() < INICIO_DEFENSA))) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean obstaculoElevado(Posicion p, boolean alArco) {
        int[] rivales = sp.balon().indicesMasCercanos(sp.rivales());
        double distPuntoBalon = sp.balon().distancia(p);
        for (int r = 0; r < rivales.length; r++) {
            int i = rivales[r];
            Posicion rival = sp.rivales()[i];
            double distRivalBalon = rival.distancia(sp.balon());
            double distRivalPunto = rival.distancia(p);
            if (i != idxPortRival && rival.getY() < 45) {
                double angBalonRival = sp.balon().angulo(rival);
                double angDisparo = sp.balon().angulo(p);
                double difAng = difAngulosRadianes(angBalonRival, angDisparo);
                if (alArco && difAng <= 10 && distRivalBalon <= distPuntoBalon) {
                    return true;
                } else if (!alArco && difAng <= 10 && distRivalBalon <= distPuntoBalon && distRivalPunto <= distPuntoBalon || distPuntoBalon < 12) {
                    return true;
                }
            }
        }
        return false;
    }

    private double difAngulosRadianes(double angA, double angB) {
        return difAngulos(angA * TO_ANG, angB * TO_ANG);

    }

    private double difAngulos(double angA, double angB) {
        if (angA < 0) {
            angA += 360;
        }
        if (angB < 0) {
            angB += 360;
        }
        double dif = angA >= angB ? angA - angB : angB - angA;
        return dif;
    }

    private Posicion getDireccionAvance(int i) {
        double ang = getMejorOpcionAvance(i);
        Posicion p = sp.misJugadores()[i];
        if (ang != -1) {
            return getDireccionAvance(p, ang);
        }
        if (p.getX() > 0) {
            ang = p.angulo(Constantes.cornerSupDer);
        } else {
            ang = p.angulo(Constantes.cornerSupIzq);
        }
        return p.moverAngulo(ang, 30).setDentroCampoJuego();
    }

    private Posicion getDireccionAvance(Posicion p, double ang) {
        if (ang == -1) {
            return null;
        } else {
            ang += p.angulo(Constantes.centroArcoSup);
        }
        return p.moverAngulo(ang, 40).setDentroCampoJuego();
    }

    private double getMejorOpcionAvance(int idx) {

        Posicion p = sp.misJugadores()[idx];
        if (p.distancia(Constantes.centroArcoSup) > 10 && p.getY() < 40) {
            double distControl = Constantes.DISTANCIA_CONTROL_BALON;
            Map<Double, Integer> dir = new LinkedHashMap<Double, Integer>();
            // angulos a tener en cuenta
            double angulos[][] = angulosIzq;
            if (p.getX() > 0) {
                angulos = angulosDer;
            }

            double remate = getVelocidadAvance(idx);
            double angArco = p.angulo(Constantes.centroArcoSup);
            for (double[] i : angulos) {
                double ang = (i[1] + i[0]) / 2;
                dir.put(ang, 0);
                Balon bAct = new Balon(sp.balon(), sp.alturaBalon());

                int iter = 0;
                // se obtienen las siguientes posiciones del balon para el angulo actual
                double dist = 0;

                Balon bSig = null;
                bSig = predecirBalon(bAct, idx, remate, ang + (angArco * TO_ANG), 0);

                for (int b = 1; b < 10; b++) {
                    iter++;
                    // if ((bSig.getPosicion().getX() + "").equals("" + Double.NaN) || (bSig.getPosicion().getY() + "").equals("" + Double.NaN)) {
                    // bSig = new Balon(bAct);
                    // }
                    dist = bAct.getPosicion().distancia(bSig.getPosicion());
                    if (dist > 0) {
                        int idxRival = bSig.getPosicion().indiceMasCercano(sp.rivales());
                        Posicion rival = sp.rivales()[idxRival];
                        double controlBalonRival = iter * Constantes.getVelocidad(sp.detalleJugadoresRivales()[idxRival].getVelocidad()) + distControl;
                        // si el rival estará en posicion de control del balon
                        if (rival.distancia(bSig.getPosicion()) <= controlBalonRival && rival.distancia(bSig.getPosicion()) < sp.misJugadores()[idx].distancia(bSig.getPosicion())) {
                            int cont = 1;
                            if (dir.containsKey(ang)) {
                                cont += dir.get(ang);
                            }
                            dir.put(ang, cont);
                            break;
                        }
                        if (dir.get(ang) > 0) {
                            break;
                        }
                        Balon tmp = new Balon(bSig);
                        bSig = obtenerInfoBalon(bAct, bSig);
                        bAct = new Balon(tmp);
                    } else {
                        break;
                    }
                }
                // si el angulo de avance está libre de rivales
                if (dir.get(ang) == 0) {
                    return ang * TO_RAD;
                }
            }
        }
        return -1;
    }

    private Balon predecirBalon(Balon bActual, int idx, double remate, double anguloH, double anguloV) {
        JugadorDetalle j = sp.detalleMisJugadores()[idx];
        double vel = remate * Constantes.getVelocidadRemate(j.getRemate());// calcula la velocidad del remate
        double angVer = Math.min(anguloV, Constantes.ANGULO_VERTICAL_MAX);
        angVer = Math.max(angVer, 0);
        angVer = angVer * TO_RAD;
        if (vel != 0) {// si el remate tiene velocidad
            vel = vel * Math.cos(angVer);
            double balonDz = Math.round((vel * Math.sin(angVer)) / Constantes.G) * Constantes.G;
            double factor = angVer > 0 ? Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE : Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
            // calcula la velocidad en el plano x/y
            double balonDx = Math.cos(anguloH * TO_RAD) * vel * factor;
            double balonDy = Math.sin(anguloH * TO_RAD) * vel * factor;
            return new Balon(bActual.getPosicion().moverPosicion(balonDx, balonDy), bActual.getAltura() + balonDz);
        }
        return bActual;
    }

    /**
     * Busca el jugador mas cercano que no tenga un rival a menos de 5 metros de distancia
     *
     * @param idx
     * @return
     */
    private int getDesmarcado(int idx) {
        int cerca[] = sp.misJugadores()[idx].indicesMasCercanos(sp.misJugadores(), idx);
        for (int i = 0; i < 10; i++) {
            Posicion mio = sp.misJugadores()[cerca[i]];
            int rival = mio.indiceMasCercano(sp.rivales());
            if (mio.getY() > sp.balon().getY() && mio.distancia(sp.rivales()[rival]) > DIST_MARCA && sp.misJugadores()[idx].distancia(mio) > 6) {
                return cerca[i];
            }
        }
        return -1;
    }

    private void sacar(int[] cercanos) {
        iterSaco++;
        if (iterSaco >= ITERACIONES_SAQUE) {
            iterSaco = 0;
            int idxPase = cercanos[1];
            if (sp.balon().getY() < Constantes.penalInf.getY()) {
                idxPase = 10;
            }
            if (sp.balon().distancia(Constantes.centroCampoJuego) == 0) {
                idxPase = 5;
            }
            comandos.add(new ComandoIrA(cercanos[0], sp.balon()));
            // tiro de esquina
            if (sp.balon().distancia(Constantes.cornerSupDer) == 0 || sp.balon().distancia(Constantes.cornerSupIzq) == 0) {
                idxPase = getDesmarcado(cercanos[0]);
                if (idxPase >= 0) {// pase por arriba
                    comandos.add(new ComandoGolpearBalon(cercanos[0], sp.misJugadores()[idxPase], 1, 38));
                } else {// despeje al lugar mas desocupado de la cancha
                    comandos.add(new ComandoGolpearBalon(cercanos[0], Constantes.penalSup, 1, 38));
                }
            } else {
                // centro del campo
                if (sp.balon().distancia(Constantes.centroCampoJuego) == 0) {
                    comandos.add(new ComandoGolpearBalon(cercanos[0], sp.misJugadores()[idxPase], getVelocidadPase(cercanos[0], idxPase), 0));
                } // lateral
                else if (Math.abs(sp.balon().getX()) == (Constantes.ANCHO_CAMPO_JUEGO / 2.0)) {
                    idxPase = getDesmarcado(cercanos[0]);
                    if (idxPase >= 0) {// pase por arriba
                        comandos.add(new ComandoGolpearBalon(cercanos[0], sp.misJugadores()[idxPase], 1, 38));
                    } else {// despeje al lugar mas desocupado de la cancha
                        comandos.add(new ComandoGolpearBalon(cercanos[0], Constantes.penalSup, 1, 38));
                    }
                } else {// otros casos (porteria)
                    Posicion pDespeje = new Posicion(-30, 0);
                    if (sp.balon().getX() > 0) {
                        pDespeje = new Posicion(30, 0);
                    }
                    comandos.add(new ComandoGolpearBalon(cercanos[0], pDespeje, 1, 38));
                }
            }
        } else {
            int idx = -1;
            for (int i : cercanos) {
                if (Constantes.getVelocidadRemate(sp.detalleMisJugadores()[i].getRemate()) == Constantes.REMATE_VELOCIDAD_MAX && i != idxMiPortero) {
                    comandos.add(new ComandoIrA(i, sp.balon()));
                    idx = i;
                    break;
                }
            }
            if (sp.balon().distancia(Constantes.cornerSupDer) == 0 || sp.balon().distancia(Constantes.cornerSupIzq) == 0) {
                int i = Constantes.penalSup.indiceMasCercano(sp.misJugadores(), idx);
                comandos.add(new ComandoIrA(i, Constantes.penalSup));
            }
        }

    }

    private boolean lanzarAlArco(int idx) {
        double distJugadorArco = sp.misJugadores()[idx].distancia(Constantes.centroArcoSup);
        Posicion porteroRival = sp.rivales()[idxPortRival];
        Posicion p = new Posicion(Constantes.centroArcoSup);
        double distPorteroJugador = sp.misJugadores()[idx].distancia(porteroRival);
        double anguloDisparo = sp.balon().angulo(p) * 180 / Math.PI;
        double distPortArco = porteroRival.distancia(p);
        if (existePaseAtras(idx, p)) {
            return true;
        }
        if (paseAtras) {
            comandos.add(new ComandoGolpearBalon(idx, getMejorPosicionDisparo(porteroRival), 1, 0));
            addEstadistica(idx, tipoOpcion.paseAngulo);
            return true;
        }
        paseAtras = false;
        if (!obstaculoElevado(p, true) && (anguloDisparo < MIN_ANGULO_DISPARO || anguloDisparo > MAX_ANGULO_DISPARO)) {
            int idxPase = Constantes.penalSup.indiceMasCercano(sp.misJugadores(), idx);
            p = sp.misJugadores()[idxPase];
            if (!obstaculoBajo(p, false)) {
                comandos.add(new ComandoGolpearBalon(idx, p, 1, 0));
                paseAtras = true;
                return true;
            } else if (!obstaculoElevado(p, false)) {
                golperBalonAlto(idx, p);
                paseAtras = true;
                return true;
            }
        }
        if (!obstaculoBajo(p, true) && distJugadorArco > MIN_DISTANCIA_TIRO_CALCULADO && Constantes.getVelocidadRemate(sp.detalleMisJugadores()[idx].getRemate()) > MIN_FUERZA_REMATADOR) {
            return dispararAlArco(idx, p);
        }
        Posicion p1 = getMejorPosicionDisparo(porteroRival);
        if (!obstaculoBajo(p1, true) && distPorteroJugador < MAX_DIST_PORT_REMATADOR && esMejorOpcionDisparo(idx, tipoOpcion.tMedio)) {
            comandos.add(new ComandoGolpearBalon(idx, p1, 1, 0));
            addEstadistica(idx, tipoOpcion.tMedio);
            return true;
        }
        if (!obstaculoElevado(p1, true) && distPortArco > 3 && esMejorOpcionDisparo(idx, tipoOpcion.globo)) {
            dispararAlArco(idx, p1);
            addEstadistica(idx, tipoOpcion.globo);
            return true;
        }
        if (!obstaculoBajo(p1, true && distPorteroJugador > MAX_DIST_PORT_REMATADOR && distJugadorArco > 12)) {
            double ang = sp.balon().angulo(p1) + getMejorOpcionAvance(idx);
            comandos.add(new ComandoGolpearBalon(idx, ang * TO_ANG, getVelocidadAvance(idx), 0));
            return true;
        }

        return false;
    }

    private boolean existePaseAtras(int idx, Posicion pDest) {
        if (!paseAtras && sp.misJugadores()[idx].getY() > 38) {
            int[] cercanos = Constantes.centroArcoSup.indicesMasCercanos(sp.misJugadores(), idx, idxMiPortero);
            double angRemate = sp.balon().angulo(pDest);
            for (int i : cercanos) {
                if (i != idx && i != idxMiPortero) {
                    Posicion p = sp.misJugadores()[i];
                    // jugador en el area de tiro
                    if (Math.abs(p.getX()) < 14 && p.getY() > 33) {
                        double angJug = p.angulo(pDest);
                        if (difAngulosRadianes(angRemate, angJug) > 45) {
                            if (!obstaculoBajo(p, false)) {
                                comandos.add(new ComandoGolpearBalon(idx, p, 1, 0));
                                paseAtras = true;
                                return true;
                            } else if (!obstaculoElevado(p, false)) {
                                golperBalonAlto(idx, p);
                                paseAtras = true;
                                return true;
                            }
                        }
                    } else {
                        break;
                    }
                }
            }
        }
        return false;
    }

    private double getVelocidadPase(int idxOrg, int idxPase) {
//		if (sp.balon().distancia(sp.misJugadores()[idxPase]) < 10) {
//			return 0.5;
//		}
        return 1;
    }

    private Posicion getMejorPosicionDisparo(Posicion porteroRival) {
        double delta = 0.35d;
        Posicion media = null;
        if (porteroRival.getY() > 51) {
            double distIzq = porteroRival.distancia(Constantes.posteIzqArcoSup);
            double distDer = porteroRival.distancia(Constantes.posteDerArcoSup);
            if (distIzq > distDer) {
                media = Constantes.posteIzqArcoSup.moverPosicion(delta, 0);
            } else {
                media = Constantes.posteDerArcoSup.moverPosicion(-delta, 0);
            }
        } else {
            double angArqIzq = porteroRival.angulo(Constantes.posteIzqArcoSup);
            double angArqDer = porteroRival.angulo(Constantes.posteDerArcoSup);

            double angBalIzq = sp.balon().angulo(Constantes.posteIzqArcoSup);
            double angBalDer = sp.balon().angulo(Constantes.posteDerArcoSup);

            double difIzq = difAngulosRadianes(angArqIzq, angBalIzq);
            double difDer = difAngulosRadianes(angArqDer, angBalDer);

            if (difIzq > difDer) {
                media = Constantes.posteIzqArcoSup.moverPosicion(delta, 0);
            } else {
                media = Constantes.posteDerArcoSup.moverPosicion(-delta, 0);
            }
        }

        return media;
    // return Constantes.centroArcoSup;
    }

    private void setAlineacion() {
        List<Integer> excluir = new ArrayList<Integer>();
        for (Comando com : comandos) {
            if (com instanceof ComandoIrA) {
                excluir.add(com.getIndJugador());
            }
        }
        double y = sp.balon().setDentroCampoJuego().getY() + Constantes.centroArcoSup.getY();
        int seccionAct = (int) Math.floor(y / seccion);
        alinear(alineaciones[seccionAct], excluir);
    }

    /** Alinea el equipo en una posicion dada */
    private void alinear(Posicion alineacion[], List<Integer> excluir) {
        for (int i = 1; i < alineacion.length; i++) {
            if (!excluir.contains(i)) {
                comandos.add(new ComandoIrA(i, alineacion[i]));
            }
        }
    }

    @Override
    public Posicion[] getPosicionSaca(SituacionPartido sp) {
        return alineacionSaca;
    }

    @Override
    public Posicion[] getPosicionRecive(SituacionPartido sp) {
        return alineacionRecibe;
    }

    /**
     * Obtiene la posicion a la cual el jugador i se tiene que dirigir para interceptar el balon lo mas pronto posible
     *
     * @param i
     *            indice del jugador [0-10]
     * @return posicion a la cual se debe dirigir el jugador
     */
    private Posicion obtenerPosDestino(int i) {
        Balon infoBalon = new Balon(sp.balon(), sp.alturaBalon());
        Posicion jugador = sp.misJugadores()[i];
        double distControl = Constantes.DISTANCIA_CONTROL_BALON;
        double altControl = Constantes.ALTURA_CONTROL_BALON;
        if (balonAnt != null && jugador.distancia(sp.balon()) > distControl) {
            int iterBalon = 0;
            int iterJugador = -1;
            Balon infoBalonAnt = new Balon(balonAnt);
            double distAntPos = jugador.distancia(infoBalonAnt.getPosicion()) - distControl;// distancia anterior
            double distNuevaPos = 0;// distancia nueva
            double velocidad = Constantes.getVelocidad(sp.detalleMisJugadores()[i].getVelocidad());
            boolean continuar = true;
            do {
                iterBalon++;
                // posicion del balon en la siguiente iteracion
                Balon infoBalonSig = obtenerInfoBalon(infoBalonAnt, infoBalon);

                // distancia del jugador a la nueva posicion
                distNuevaPos = jugador.distancia(infoBalonSig.getPosicion()) - distControl;
                // si la distancia siguiente es mayor a la anterior, es porque el balon se está alejando del jugador
                if (distAntPos < distNuevaPos) {
                    return infoBalonSig.getPosicion();
                }
                // si el jugador estará en control del balon (por altura) en la iteracion evaluada
                if (infoBalonSig.getAltura() <= altControl) {

                    // iteraciones que le tomaría al jugador el llegar a la siguiente posicion del balon
                    iterJugador = (int) Math.ceil(distNuevaPos / velocidad);

                    // si las iteraciones que le tomaría al jugador llegar a la siguiente posicion son mayores a las que tomaría al balon llegar a la misma pos
                    if (iterJugador <= iterBalon) {
                        return infoBalonSig.getPosicion();
                    }
                }
                infoBalonAnt = new Balon(infoBalon);
                infoBalon = new Balon(infoBalonSig);
                distAntPos = distNuevaPos;
            } while (continuar);
        }
        return infoBalon.getPosicion();
    }

    /**
     * Retorna la posicion del balon en la siguiente iteracion
     *
     * @return
     */
    private Balon obtenerPosBalon() {
        return obtenerInfoBalon(balonAnt, new Balon(sp.balon(), sp.alturaBalon()));
    }

    /**
     * Retorna la posicion del balon en la siguiente iteracion tomando como punto de referencia el pasado como parametro
     *
     * @param posAnt
     * @return
     */
    private Balon obtenerInfoBalon(Balon infoBalonAnt, Balon infoBalonAct) {
        Posicion p = new Posicion(infoBalonAct.getPosicion());
        if (infoBalonAnt != null) {
            if (sp.saco() || sp.sacaRival() || infoBalonAct.getPosicion().distancia(Constantes.centroCampoJuego) == 0) {
                return new Balon(infoBalonAct.getPosicion(), 0);
            }
            double angulo = infoBalonAnt.getPosicion().angulo(infoBalonAct.getPosicion());
            double dx = infoBalonAct.getPosicion().getX() - infoBalonAnt.getPosicion().getX();
            double dy = infoBalonAct.getPosicion().getY() - infoBalonAnt.getPosicion().getY();
            double vel = 1;

            double dz = infoBalonAct.getAltura() - infoBalonAnt.getAltura();
            double distancia = infoBalonAct.getPosicion().distancia(infoBalonAnt.getPosicion());
            double gravedad = 0;
            double angVer = 0;
            double nuevaAltura = 0;
            if (infoBalonAct.getAltura() == 0) {
                vel = Math.sqrt(dx * dx + dy * dy) * Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
            } else {
                gravedad = Constantes.G;
                vel = Math.sqrt(dz * dz + distancia * distancia) * Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
                angVer = Math.atan(dz / distancia);
                vel = vel * Math.cos(angVer);
            }
            dx = Math.cos(angulo) * vel;
            dy = Math.sin(angulo) * vel;
            p = p.moverPosicion(dx, dy);
            if (infoBalonAct.getAltura() > 0) {
                distancia = p.distancia(infoBalonAct.getPosicion());
                vel = Math.sqrt(dz * dz + distancia * distancia) * Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
                angVer = Math.atan(dz / distancia);
                dz = Math.round(vel * Math.sin(angVer) / Constantes.G) * Constantes.G;
                nuevaAltura = Math.round((infoBalonAct.getAltura() + dz - gravedad) / Constantes.G) * Constantes.G;
            }
            Balon bSig = new Balon(p.setDentroCampoJuego(), nuevaAltura < 0 ? 0 : nuevaAltura);
            if ((bSig.getPosicion().getX() + "").equals("" + Double.NaN) || (bSig.getPosicion().getY() + "").equals("" + Double.NaN)) {
                bSig = new Balon(infoBalonAct.getPosicion(), 0);
            }
            return bSig;
        }
        return infoBalonAct;
    }

    private void golperBalonAlto(int jugador, Posicion p) {
        double remate = sp.detalleMisJugadores()[jugador].getRemate();
        double xMedios = (sp.balon().distancia(p) / 2) + 2;
        // punto de Y maximo
        double y = ALTURA_MAXIMA_REMATE;
        // teta=atan(4y/x)
        double angulo = Math.atan(2 * y / xMedios);
        // Vo= raiz(2yg)/sen(angulo)
        double velocidad = Math.sqrt(2 * y * Constantes.G) / Math.sin(angulo);
        if (velocidad <= Constantes.getVelocidadRemate(remate) + 0.1d) {
            velocidad = velocidad / Constantes.getVelocidadRemate(remate);
            comandos.add(new ComandoGolpearBalon(jugador, p, velocidad, angulo * TO_ANG));
        } else {
            double fVel = Constantes.getVelocidadRemate(remate);
            double distancia = sp.misJugadores()[jugador].distancia(p) * 1.4;
            velocidad = Math.sqrt(distancia * Constantes.G / Math.sin(2 * angulo * TO_RAD));
            velocidad = velocidad / fVel;
            comandos.add(new ComandoGolpearBalon(jugador, p, velocidad, angulo));
        }
    }

    private boolean dispararAlArco(int jugador, Posicion p) {
        double remate = sp.detalleMisJugadores()[jugador].getRemate();
        if (esMejorOpcionDisparo(jugador, tipoOpcion.tCalculado)) {
            double xMedios = sp.balon().distancia(p) - 4d;
            // punto de Y maximo
            double y = ALTURA_MAXIMA_REMATE;
            // teta=atan(4y/x)
            double angulo = Math.atan(2 * y / xMedios);
            // Vo= raiz(2yg)/sen(angulo)
            double velocidad = Math.sqrt(2 * y * Constantes.G) / Math.sin(angulo);
            if (velocidad <= Constantes.getVelocidadRemate(remate) + 0.1d) {
                velocidad = velocidad / Constantes.getVelocidadRemate(remate);
                comandos.add(new ComandoGolpearBalon(jugador, p, velocidad, angulo * TO_ANG));
                addEstadistica(jugador, tipoOpcion.tCalculado);
                return true;
            }
        }
        if (esMejorOpcionDisparo(jugador, tipoOpcion.tNoCalculado)) {
            comandos.add(new ComandoGolpearBalon(jugador, p, 1, 26.7d - (sp.balon().distancia(p) * 0.24d)));
            addEstadistica(jugador, tipoOpcion.tNoCalculado);
            return true;
        }
        return false;
    }

    private void addEstadistica(int idx, tipoOpcion tOpcion) {
        ultimoTiro = tOpcion;
        ultimoJugador = idx;
        LinkedHashMap<tipoOpcion, Estadistica> opciones = estadisticas.get(idx);
        if (opciones == null) {
            opciones = new LinkedHashMap<tipoOpcion, Estadistica>();
            opciones.put(tOpcion, new Estadistica());
        }
        Estadistica est = opciones.get(tOpcion);
        if (est == null) {
            est = new Estadistica();
        }
        est.addOpcion();
        opciones.put(tOpcion, est);
        estadisticas.put(idx, opciones);
    }

    private boolean esMejorOpcionDisparo(int idx, tipoOpcion tOpcion) {
        LinkedHashMap<tipoOpcion, Estadistica> opciones = estadisticas.get(idx);
        if (opciones != null) {
            Estadistica estadistica = opciones.get(tOpcion);
            if (estadistica != null) {
                if ((estadistica.getOpciones() >= 5 && estadistica.getGoles() < 1) || estadistica.getDescartes() > 0) {
                    if (estadistica.getDescartes() < 3) {
                        estadistica.addDescarte();
                    } else {
                        estadistica.setOpciones(1);
                        estadistica.setDescartes(0);
                    }
                    return false;
                }
            }
        }
        return true;
    }

    private int getPosFinalBalon() {
        Balon infoBalon = new Balon(sp.balon(), sp.alturaBalon());
        int idxCercaIni = sp.balon().indiceMasCercano(sp.misJugadores());
        double distIni = sp.balon().distancia(sp.misJugadores()[idxCercaIni]);
        if (balonAnt != null) {
            Balon infoBalonAnt = new Balon(balonAnt);
            int i = 0;
            do {
                i++;
                // posicion del balon en la siguiente iteracion
                Balon infoBalonSig = obtenerInfoBalon(infoBalonAnt, infoBalon);

                if (infoBalonSig.getAltura() <= Constantes.ALTURA_CONTROL_BALON) {
                    double distControl = Constantes.DISTANCIA_CONTROL_BALON + (i * Constantes.VELOCIDAD_MAX);
                    int[] idxCerca = infoBalonSig.getPosicion().indicesMasCercanos(sp.misJugadores());
                    for (int j = 0; j < 11; j++) {
                        double distAct = sp.misJugadores()[idxCerca[j]].distancia(infoBalonSig.getPosicion());
                        if ((idxCercaIni != idxCerca[j] || distAct < distIni) && distAct < distControl) {
                            comandos.add(new ComandoIrA(idxCerca[j], infoBalonSig.getPosicion()));
                            return idxCerca[j];
                        }
                    }
                }
                infoBalonAnt = new Balon(infoBalon);
                infoBalon = new Balon(infoBalonSig);
            } while (i <= ITERACIONES_AVANCE_BALON && infoBalon.getPosicion().distancia(infoBalonAnt.getPosicion()) > 0.4d);
        }
        return -1;
    }
}